using System;
using System.IO;
using System.Security.Cryptography;

namespace nToolbox.Security
{
    /// <summary>
    /// Utility class that provides static methods for encrypting to/from strings using
    /// a provided SymmetricAlgorithm.
    /// </summary>
    /// <seealso cref="System.Security.Cryptography.SymmetricAlgorithm"/>
    public static class SymmetricCryptography
    {
        /// <summary>
        /// Encrypt a string to a Base 64 string using the provided SymmetricAlgorithm.
        /// </summary>
        public static string Encrypt( SymmetricAlgorithm algorithm, string clearText )
        {
            string encryptedText;

            using ( MemoryStream ms = new MemoryStream() )
            {
                using ( CryptoStream cs = new CryptoStream( ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write ) )
                {
                    using ( StreamWriter sw = new StreamWriter( cs ) )
                    {
                        sw.Write( clearText );
                    }
                }

                encryptedText = Convert.ToBase64String( ms.ToArray() );
            }

            return encryptedText;
        }

        /// <summary>
        /// Decrypt a Base 64 string from a string using the provided SymmetricAlgorithm.  Meant to be used
        /// on strings that were originally encrypted with Encrypt using the same algorithm.
        /// </summary>
        public static string Decrypt( SymmetricAlgorithm algorithm, string encryptedText )
        {
            string clearText;

            using ( MemoryStream ms = new MemoryStream( Convert.FromBase64String( encryptedText ) ) )
            {
                using ( CryptoStream cs = new CryptoStream( ms, algorithm.CreateDecryptor(), CryptoStreamMode.Read ) )
                {
                    using ( StreamReader sr = new StreamReader( cs ) )
                    {
                        clearText = sr.ReadToEnd();
                    }
                }
            }

            return clearText;
        }

        /// <summary>
        /// Encrypt a string to a stream.
        /// </summary>
        public static void EncryptToStream( SymmetricAlgorithm algorithm, string clearText, Stream outputStream )
        {
            CryptoStream cs = new CryptoStream( outputStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write );
            using ( StreamWriter sw = new StreamWriter( cs ) )
            {
                sw.Write( clearText );
            }
        }

        /// <summary>
        /// Decrypt a string out of a stream.  Intended to be used on streams that were originally encrypted
        /// with EncryptToStream using the same algorithm.
        /// </summary>
        public static string DecryptFromStream( SymmetricAlgorithm algorithm, Stream inputStream )
        {
            CryptoStream cs = new CryptoStream( inputStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read );
            using ( StreamReader reader = new StreamReader( inputStream ) )
            {
                return reader.ReadToEnd();
            }
        }

        private const int BUFFERSIZE = 1024;
        /// <summary>
        /// Encrypt the contents of a stream into another stream.  The output stream is left open.
        /// </summary>
        public static void Encrypt( SymmetricAlgorithm algorithm, Stream inputStream, Stream outputStream )
        {
            CryptoStream cs = new CryptoStream( outputStream, algorithm.CreateEncryptor(), CryptoStreamMode.Write );
            byte[] buffer = new byte[BUFFERSIZE];
            int count;
            for ( int offset = 0; offset < inputStream.Length; offset += count )
            {
                count = inputStream.Read( buffer, offset, BUFFERSIZE );
                cs.Write( buffer, 0, count );
            }
            cs.Close();
        }

        /// <summary>
        /// Decrypt the contents of a Stream into another Stream.  Meant to be used on Streams that were originally
        /// encrypted with Encrypt using the same algorithm. 
        /// </summary>
        public static void Decrypt( SymmetricAlgorithm algorithm, Stream inputStream, Stream outputStream )
        {
            CryptoStream cs = new CryptoStream( inputStream, algorithm.CreateDecryptor(), CryptoStreamMode.Read );
            // have to read by byte since we can't get the CryptoStream length, and it's length may be different than the input stream length.
            int count = cs.ReadByte();
            while ( count >= 0 )
            {
                outputStream.WriteByte( (byte)count );
                count = cs.ReadByte();
            }
            cs.Close();
        }

        /*  These methods don't work, although it looks like they should.  Will work on later.
        /// <summary>
        /// Encrypt the contents of a byte array using the provided SymmetricAlgorithm.
        /// </summary>
        public static byte[] Encrypt( SymmetricAlgorithm algorithm, byte[] input )
        {
            using ( MemoryStream ms = new MemoryStream() )
            {
                using ( CryptoStream cs = new CryptoStream( ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write ) )
                {
                    cs.Write( input, 0, input.Length );
                    cs.Flush();
                    return ms.ToArray();
                }
            }
        }

        /// <summary>
        /// Decrypt the contents of a byte array into a new byte array.  Meant to be used on Streams that were originally
        /// encrypted with Encrypt using the same algorithm.
        /// </summary>
        public static byte[] Decrypt( SymmetricAlgorithm algorithm, byte[] input )
        {
            using ( MemoryStream ms = new MemoryStream( input ) )
            {
                using ( MemoryStream outs = new MemoryStream() )
                {
                    Decrypt( algorithm, ms, outs );
                    return outs.ToArray();
                }
            }
        }
        */
    }
}
